#region License

// The MIT License
//
// Copyright (c) 2006-2008 DevDefined Limited.
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web;
using QueryParameter = System.Collections.Generic.KeyValuePair<string, string>;

namespace DevDefined.OAuth.Framework
{
  [Serializable]
  public class OAuthContext : IOAuthContext
  {
    readonly BoundParameter _callbackUrl;
    readonly BoundParameter _consumerKey;
    readonly BoundParameter _nonce;
    readonly BoundParameter _signature;
    readonly BoundParameter _signatureMethod;
    readonly BoundParameter _timestamp;
    readonly BoundParameter _token;
    readonly BoundParameter _tokenSecret;
    readonly BoundParameter _verifier;
    readonly BoundParameter _version;
    NameValueCollection _authorizationHeaderParameters;
    NameValueCollection _cookies;
    NameValueCollection _formEncodedParameters;
    NameValueCollection _headers;
    string _normalizedRequestUrl;
    NameValueCollection _queryParameters;
    Uri _rawUri;

    public OAuthContext()
    {
      _verifier = new BoundParameter(Parameters.OAuth_Verifier, this);
      _consumerKey = new BoundParameter(Parameters.OAuth_Consumer_Key, this);
      _callbackUrl = new BoundParameter(Parameters.OAuth_Callback, this);
      _nonce = new BoundParameter(Parameters.OAuth_Nonce, this);
      _signature = new BoundParameter(Parameters.OAuth_Signature, this);
      _signatureMethod = new BoundParameter(Parameters.OAuth_Signature_Method, this);
      _timestamp = new BoundParameter(Parameters.OAuth_Timestamp, this);
      _token = new BoundParameter(Parameters.OAuth_Token, this);
      _tokenSecret = new BoundParameter(Parameters.OAuth_Token_Secret, this);
      _version = new BoundParameter(Parameters.OAuth_Version, this);

      FormEncodedParameters = new NameValueCollection();
      Cookies = new NameValueCollection();
      Headers = new NameValueCollection();
      AuthorizationHeaderParameters = new NameValueCollection();
    }

    public NameValueCollection Headers
    {
      get
      {
        if (_headers == null) _headers = new NameValueCollection();
        return _headers;
      }
      set { _headers = value; }
    }

    public NameValueCollection QueryParameters
    {
      get
      {
        if (_queryParameters == null) _queryParameters = new NameValueCollection();
        return _queryParameters;
      }
      set { _queryParameters = value; }
    }

    public NameValueCollection Cookies
    {
      get
      {
        if (_cookies == null) _cookies = new NameValueCollection();
        return _cookies;
      }
      set { _cookies = value; }
    }

    public NameValueCollection FormEncodedParameters
    {
      get
      {
        if (_formEncodedParameters == null) _formEncodedParameters = new NameValueCollection();
        return _formEncodedParameters;
      }
      set { _formEncodedParameters = value; }
    }

    public NameValueCollection AuthorizationHeaderParameters
    {
      get
      {
        if (_authorizationHeaderParameters == null) _authorizationHeaderParameters = new NameValueCollection();
        return _authorizationHeaderParameters;
      }
      set { _authorizationHeaderParameters = value; }
    }

    public Uri RawUri
    {
      get { return _rawUri; }
      set
      {
        _rawUri = value;

        NameValueCollection newParameters = HttpUtility.ParseQueryString(_rawUri.Query);

        // TODO: tidy this up, bit clunky

        foreach (string parameter in newParameters)
        {
          QueryParameters[parameter] = newParameters[parameter];
        }

        _normalizedRequestUrl = UriUtility.NormalizeUri(_rawUri);
      }
    }

    public string NormalizedRequestUrl
    {
      get { return _normalizedRequestUrl; }
    }

    public string RequestMethod { get; set; }

    public string Nonce
    {
      get { return _nonce.Value; }
      set { _nonce.Value = value; }
    }

    public string Verifier
    {
      get { return _verifier.Value; }
      set { _verifier.Value = value; }
    }

    public string CallbackUrl
    {
      get { return _callbackUrl.Value; }
      set { _callbackUrl.Value = value; }
    }

    public string Signature
    {
      get { return _signature.Value; }
      set { _signature.Value = value; }
    }

    public string SignatureMethod
    {
      get { return _signatureMethod.Value; }
      set { _signatureMethod.Value = value; }
    }

    public string Timestamp
    {
      get { return _timestamp.Value; }
      set { _timestamp.Value = value; }
    }

    public string Version
    {
      get { return _version.Value; }
      set { _version.Value = value; }
    }

    public bool UseAuthorizationHeader { get; set; }

    public string Realm
    {
      get { return AuthorizationHeaderParameters[Parameters.Realm]; }
      set { AuthorizationHeaderParameters[Parameters.Realm] = value; }
    }

    public string ConsumerKey
    {
      get { return _consumerKey.Value; }
      set { _consumerKey.Value = value; }
    }

    public string Token
    {
      get { return _token.Value; }
      set { _token.Value = value; }
    }

    public string TokenSecret
    {
      get { return _tokenSecret.Value; }
      set { _tokenSecret.Value = value; }
    }

    public Uri GenerateUri()
    {
      var builder = new UriBuilder(NormalizedRequestUrl);

      IEnumerable<QueryParameter> parameters = QueryParameters.ToQueryParametersExcludingTokenSecret();

      builder.Query = UriUtility.FormatQueryString(parameters);

      return builder.Uri;
    }

    public string GenerateUrl()
    {
      var builder = new UriBuilder(NormalizedRequestUrl);

      builder.Query = "";

      return builder.Uri + "?" + UriUtility.FormatQueryString(QueryParameters);
    }

    public string GenerateOAuthParametersForHeader()
    {
      var builder = new StringBuilder();

      if (Realm != null) builder.Append("realm=\"").Append(Realm).Append("\"");

      IEnumerable<QueryParameter> parameters = AuthorizationHeaderParameters.ToQueryParametersExcludingTokenSecret();

      foreach (
        var parameter in parameters.Where(p => p.Key != Parameters.Realm)
        )
      {
        if (builder.Length > 0) builder.Append(",");
        builder.Append(UriUtility.UrlEncode(parameter.Key)).Append("=\"").Append(
          UriUtility.UrlEncode(parameter.Value)).Append("\"");
      }

      builder.Insert(0, "OAuth ");

      return builder.ToString();
    }

    public Uri GenerateUriWithoutOAuthParameters()
    {
      var builder = new UriBuilder(NormalizedRequestUrl);

      IEnumerable<QueryParameter> parameters = QueryParameters.ToQueryParameters()
        .Where(q => !q.Key.StartsWith(Parameters.OAuthParameterPrefix));

      builder.Query = UriUtility.FormatQueryString(parameters);

      return builder.Uri;
    }


    public string GenerateSignatureBase()
    {
      if (string.IsNullOrEmpty(ConsumerKey))
      {
        throw Error.MissingRequiredOAuthParameter(this, Parameters.OAuth_Consumer_Key);
      }

      if (string.IsNullOrEmpty(SignatureMethod))
      {
        throw Error.MissingRequiredOAuthParameter(this, Parameters.OAuth_Signature_Method);
      }

      if (string.IsNullOrEmpty(RequestMethod))
      {
        throw Error.RequestMethodHasNotBeenAssigned("RequestMethod");
      }

      var allParameters = new List<QueryParameter>();

      if (FormEncodedParameters != null) allParameters.AddRange(FormEncodedParameters.ToQueryParametersExcludingTokenSecret());
      if (QueryParameters != null) allParameters.AddRange(QueryParameters.ToQueryParametersExcludingTokenSecret());
      if (Cookies != null) allParameters.AddRange(Cookies.ToQueryParametersExcludingTokenSecret());
      if (AuthorizationHeaderParameters != null)
        allParameters.AddRange(AuthorizationHeaderParameters.ToQueryParametersExcludingTokenSecret().Where(q => q.Key != Parameters.Realm));

      allParameters.RemoveAll(param => param.Key == Parameters.OAuth_Signature);

      string signatureBase = UriUtility.FormatParameters(RequestMethod, new Uri(NormalizedRequestUrl), allParameters);

      return signatureBase;
    }

    class BoundParameter
    {
      readonly OAuthContext _context;
      readonly string _name;

      public BoundParameter(string name, OAuthContext context)
      {
        _name = name;
        _context = context;
      }

      public string Value
      {
        get { return Collection[_name]; }
        set
        {
          if (value == null)
          {
            Collection.Remove(_name);
          }
          else
          {
            Collection[_name] = value;
          }
        }
      }

      NameValueCollection Collection
      {
        get
        {
          if (_context.UseAuthorizationHeader) return _context.AuthorizationHeaderParameters;
          if (_context.RequestMethod != "GET") return _context.FormEncodedParameters;
          return _context.QueryParameters;
        }
      }
    }
  }
}